/**
 *用于替换node.event.EventEmitter，好处在于方便管理！
 *使用tag而不使用func来做查找，这样更方便一些；on当tag相同会覆盖，这样不容易出错！
 *重入的问题：
 *在emit中添加event，如果相同tag会替换；否则加入到末尾并且本次不会被调用
 *在emit中删除event，会把自己的index置为null
 *在emit中release自身，没有问题，但是之后的event不会调用
 */

// import { strict as assert } from 'assert';
// import { Pool, PoolObject } from './Pool';
// import { CallObject, EventCallbackFunc } from './CallObject';
// import { runtimeError } from './log';
// import { CostGuards } from './profile';
// import type { msgData } from './Utils';

const emitguards = new CostGuards('EmitGuards');
function getEmitGuard(): CostGuards {
	return emitguards;
}

const eventName = 'EventCallback';

class EventCallback extends CallObject {
	event: string;

	initEvent(
		event: string,
		tag: string,
		func: EventCallbackFunc,
		self: unknown,
		guards: CostGuards,
	): void {
		this.event = event;
		this.initCall(tag, func, self, guards);
	}

	release(): void {
		if (this.guard) {
			this.guard = undefined;
			Pool.ins().push(this);
		} else {
			runtimeError(`EventCallback.release twice.${this.event} ${this._name}`);
		}
	}
}

class EventCallbackArray extends PoolObject {
	event: string;

	// events:Events;
	guards: CostGuards;

	cbs: EventCallback[] = [];

	initArray(event: string, guards: CostGuards): void {
		this.event = event;
		this.guards = guards;

		this.cbs = [];
	}

	release(): void {
		for (const cb of this.cbs) {
			if (cb) {
				cb.release();
			}
		}
		this.cbs.length = 0;
		this.guards = null;
		super.release();
	}

	inemit: boolean;

	emit(args: msgData): number {
		if (this.inemit) {
			console.error('emit when this.inemit', this.event);
			return 0;
		}
		this.inemit = true;
		const rt = this._emit(args);

		const len = this.cbs.length; // 先取出len
		for (let i = len - 1; i >= 0; i--) {
			if (this.cbs[i] == null) {
				this.cbs.splice(i, 1);
			}
		}

		this.inemit = false;
		return rt;
	}

	private _emit(args: msgData): number {
		const len = this.cbs.length; // 先取出len
		let call = 0;
		for (let i = 0; i < len; i++) {
			const cb = this.cbs[i];
			if (cb) {
				cb.onCallEvent(args);
				call += 1;
			}
		}
		return call;
	}

	onEvent(func: EventCallbackFunc, self: unknown, tag: string): EventCallback {
		// tag唯一
		assert(this.guards != null);

		const funcs = this.cbs;
		for (const f of funcs) {
			if (f && f._name === tag) {
				if (f.call !== func || f.self !== self) {
					f.call = func;
					f.self = self;
					console.warn('Event.on replaced', tag);
				}
				return f;
			}
		}

		const cb: EventCallback = Pool.ins().pop('_on', eventName, EventCallback);
		cb.initEvent(this.event, tag, func, self, this.guards);

		funcs.push(cb);
		return cb;
	}

	removeListeners(): void {
		for (const cb of this.cbs) {
			if (cb) cb.release();
		}
		this.cbs.length = 0;
	}

	removeTag(tag: string): boolean {
		const index = this.cbs.indexIf((a) => a && a._name === tag);
		if (index >= 0) {
			const rt = this.cbs[index];
			rt.release();
			this.cbs[index] = null;
			return true;
		}
		return false;
	}
}

// type EventMap = Map<string,EventCallbackArray>

// node.js like EventEmit
// 不建议继承此类，而是用组合的方法
class Events extends PoolObject {
	constructor(dispatch: unknown) {
		super();
		assert(dispatch);
		this.dispatch = dispatch;
		// this.listeners = new Map<string,EventCallbackArray>  ();
		this.guards = emitguards;
	}

	dispatch: unknown;

	// private listeners: EventMap ;
	private listeners: { [key: string]: EventCallbackArray } = {};

	listenerLength(event: string): number {
		const funcs = this.listeners[event];
		if (!funcs) return 0;
		return funcs.cbs.count();
	}

	guards: CostGuards;

	setGuards(guards: CostGuards = null): void {
		this.guards = guards;
	}

	setProfile(profile: boolean, printns: number): void {
		this.guards.setProfile(profile, printns);
	}

	traceProfile(top = 10, s = 0, per = false, logfunc = console.warn): void {
		this.guards.trace(top, s, per, logfunc);
	}

	release(): void {
		this.removeListeners();
		for (const funcs of Object.values(this.listeners)) {
			funcs.release();
		}
		this.listeners = {};
		this.guards = null;
		this.dispatch = null;
		super.release();
	}

	once(event: string, func: EventCallbackFunc, self: unknown, tag: string): this {
		// 无法通过匿名函数来实现once，否则就是去了profile的意义！
		return this.on(
			event,
			(...args: any[]) => {
				this.removeListener(event, tag);
				func.apply(self, args);
			},
			null,
			tag,
		);
		// return this._on(event,func,self,this.once_listeners,tag,true);
	}

	// 危险，如果在once_until中release而又返回false，会导致出错！
	onceUntil(
		event: string,
		listener: (...args: any[]) => boolean,
		self: unknown,
		tag: string,
	): this {
		return this.on(
			event,
			(...args: any[]) => {
				const rt = listener.apply(self, args);
				if (rt === true) this.removeListener(event, tag);
			},
			null,
			tag,
		);
	}

	removeListeners(): void {
		for (const funcs of Object.values(this.listeners)) {
			funcs.removeListeners();
		}
	}

	// 在_on中可以再次_on因为总是把func加到末尾！
	// 并且emit遍历用的是length，所以本次不会被调用！
	on(event: string, func: EventCallbackFunc, self: unknown, tag: string): this {
		let funcs = this.listeners[event];
		if (!funcs) {
			funcs = Pool.ins().pop('_on', 'EventCallbackArray', EventCallbackArray);
			funcs.initArray(event, this.guards);
			this.listeners[event] = funcs;
		}
		funcs.onEvent(func, self, tag);
		return this;
	}

	emitArgs(event: string, args: msgData): number {
		assert(this.dispatch);
		args.unshift(this.dispatch);

		let r1 = 0;
		const funcs = this.listeners[event];
		if (funcs) r1 = funcs.emit(args);
		return r1;
	}

	emit(event: string, ...args: any[]): number {
		return this.emitArgs(event, args);
	}

	removeListener(event: string, tag: string): boolean {
		const l = this.listeners;
		const funcs = l[event];
		if (funcs) return funcs.removeTag(tag);
		return false;
	}

	removeTag(tag: string): boolean {
		let rt = false;
		for (const funcs of Object.values(this.listeners)) {
			rt = funcs.removeTag(tag) || rt;
		}
		return rt;
	}

	static dumpPool(): void {
		Pool.ins().dumpPool(eventName);
		Pool.ins().dumpPool('EventCallbackArray');
	}

	// 查看某个event的使用！
	static dumpEvent(name: string): void {
		Pool.ins().dumpName(eventName, name);
	}

	static createEvents(name: string, dispatch: unknown): Events {
		const rt: Events = Pool.ins().pop(name, 'Events', Events, dispatch);
		rt.dispatch = dispatch;
		rt.setGuards(emitguards);
		return rt;
	}
}
